/*
 * Copyright (c) 2009 Tim Mead
 *
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use,
 * copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following
 * conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 *
 */

/**
 * A custom Geocoder which intercepts requests to facilitate local caching using the database.
 */
package com.misoneism.fire.map
{
	import com.google.maps.LatLng;
	import com.google.maps.LatLngBounds;
	import com.google.maps.interfaces.IClientGeocoder;
	import com.google.maps.services.ClientGeocoderOptions;
	import com.google.maps.services.GeocodingEvent;
	import com.google.maps.services.GeocodingResponse;
	import com.google.maps.services.Placemark;
	import com.misoneism.fire.database.dao.IGeocodeDAO;
	import com.misoneism.fire.database.tuples.Geocode;
	
	import flash.events.Event;
	import flash.events.EventDispatcher;

	public class DatabaseGeocoder
			extends EventDispatcher
			implements IClientGeocoder
	{
		public static const DATABASE_GEOCODE_REQUEST:String = "databaseGeocode";
		
		/** The Google Geocoder used to translate addresses. */
		private var geocoder:IClientGeocoder;
		
		/** DAO for geocode-related functions. */
		private var geocodeDao:IGeocodeDAO;
		
		public function DatabaseGeocoder(geocoder:IClientGeocoder, geocodeDao:IGeocodeDAO)
		{
			this.geocoder = geocoder;
			this.geocodeDao = geocodeDao;
			
			this.geocoder.addEventListener(GeocodingEvent.GEOCODING_SUCCESS, onGeocodingSuccess);
		}

		/**
		 * Passthrough to ClientGeocoder.
		 */
		public function getViewport():LatLngBounds
		{
			return geocoder.getOptions().viewport;
		}
		
		/**
		 * Passthrough to ClientGeocoder.
		 */
		public function resetCache():void
		{
			geocoder.resetCache();
		}
		
		public function geocode(address:String):void
		{
			// First we try and find if we've cached the address
			var geocode:Geocode = geocodeDao.getGeocode(address);
			if (geocode != null)
			{
				dispatchEvent(generateGeocodingEvent(address, geocode.latitude, geocode.longitude));
				
				return;
			}
			
			// Otherwise we let the Google Geocoder resolve it for us
			geocoder.geocode(address);
		}
		
		/**
		 * Passthrough to ClientGeocoder.
		 */
		public function reverseGeocode(arg0:LatLng):void
		{
			geocoder.reverseGeocode(arg0);
		}
		
		/**
		 * Passthrough to ClientGeocoder.
		 */
		public function setOptions(arg0:ClientGeocoderOptions):void
		{
			geocoder.setOptions(arg0);
		}
		
		/**
		 * Passthrough to ClientGeocoder.
		 */
		public function getOptions():ClientGeocoderOptions
		{
			return geocoder.getOptions();
		}
		
		/**
		 * Passthrough to ClientGeocoder.
		 */
		public function setBaseCountryCode(arg0:String):void
		{
			geocoder.getOptions().countryCode = arg0;
		}
		
		/**
		 * Passthrough to ClientGeocoder.
		 */
		public function getBaseCountryCode():String
		{
			return geocoder.getOptions().countryCode;
		}
		
		/**
		 * Passthrough to ClientGeocoder.
		 */
		public function setViewport(arg0:LatLngBounds):void
		{
			geocoder.getOptions().viewport = arg0;
		}
		
		/**
		 * Passthrough to ClientGeocoder.
		 */
		public function getBaseEventDispatcher():Object
		{
			return geocoder.getBaseEventDispatcher();
		}
		
		/**
		 * Passthrough to ClientGeocoder.
		 */
		public function get interfaceChain():Array
		{
			return geocoder.interfaceChain;
		}
		
		/**
		 * Passthrough to ClientGeocoder.
		 */
		public function get wrapper():Object
		{
			return geocoder.wrapper;
		}
		
		/**
		 * Passthrough to ClientGeocoder.
		 */
		public function set wrapper(arg0:Object):void
		{
			geocoder.wrapper = arg0;
		}
		
		/**
		 * Passthrough to ClientGeocoder.
		 */
		public override function addEventListener(type:String, listener:Function, useCapture:Boolean=false, priority:int=0, useWeakReference:Boolean=false):void
		{
			geocoder.addEventListener(type, listener, useCapture, priority, useWeakReference);
		}
		
		/**
		 * Passthrough to ClientGeocoder.
		 */
		public override function removeEventListener(type:String, listener:Function, useCapture:Boolean=false):void
		{
			geocoder.removeEventListener(type, listener, useCapture);
		}
		
		/**
		 * Passthrough to ClientGeocoder.
		 */
		public override function dispatchEvent(event:Event):Boolean
		{
			return geocoder.dispatchEvent(event);
		}
		
		/**
		 * Passthrough to ClientGeocoder.
		 */
		public override function hasEventListener(type:String):Boolean
		{
			return geocoder.hasEventListener(type);
		}
		
		/**
		 * Passthrough to ClientGeocoder.
		 */
		public override function willTrigger(type:String):Boolean
		{
			return geocoder.willTrigger(type);
		}
		
		/**
		 * Callback for a geocode request successfully completing. Here we aim to insert
		 * geocode lookups into the local database.
		 */
		protected function onGeocodingSuccess(event:GeocodingEvent):void
		{
			// If we had a regular geocode event, then store the result in the database
			if (event.request == "geocode")
			{
				var latLng:LatLng = event.response.placemarks[0].point as LatLng;
				
				geocodeDao.insertGeocode(event.name, latLng.lat(), latLng.lng());
			}
		}
		
		/**
		 * Generate a GeocodingEvent based on the specified address and coordinate
		 * details.
		 */
		protected function generateGeocodingEvent(address:String, latitude:Number,
				longitude:Number):GeocodingEvent
		{
			var placemark:Placemark = new Placemark();
			placemark.address = address;
			placemark.point = new LatLng(latitude, longitude);
			
			var geocodingResponse:GeocodingResponse = new GeocodingResponse();
			geocodingResponse.name = address;
			geocodingResponse.placemarks = [placemark];
			
			var event:GeocodingEvent = new GeocodingEvent(GeocodingEvent.GEOCODING_SUCCESS);
			event.name = address;
			event.request = DATABASE_GEOCODE_REQUEST;
			event.response = geocodingResponse;
			
			return event;
		}
	}
}